package com.cyw.common.config.aspects;

import com.cyw.common.annotations.SupressAspectLog;
import com.cyw.common.utils.JsonUtils;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Aspect
public class BizSystemLogAspect {

    private static final Logger LOGGER = LoggerFactory.getLogger(BizSystemLogAspect.class);

    private static final int DEFAULT_PARAM_MAX_LENGTH = 2048;

    private final String scanApiPackageName = "execution(* com.ddll..api.impl..*.*(..))";
    private final String scanBizPackageName = "|| execution(* com.ddll..biz.impl..*.*(..))";

    /**
     * 参数输出的最大长度，超过该长度就被截断
     */
    private int paramMaxOutputLength;


    private static Class<?> multipartFileClazz;

    static {
        try {
            multipartFileClazz = Class.forName("org.springframework.web.multipart.MultipartFile");
            LOGGER.debug("multipartFileClazz:{}", multipartFileClazz);
        } catch (ClassNotFoundException e) {
            LOGGER.debug("class not found:{}", e.getMessage());
        }
    }

    public BizSystemLogAspect() {
        this.paramMaxOutputLength = DEFAULT_PARAM_MAX_LENGTH;
    }

    @Pointcut(scanApiPackageName + scanBizPackageName)
    public void serviceAspect() {
    }

    /**
     * 环绕通知
     * 1、打印方法输入输出参数
     * 2、记录方法执行时间
     * @author sxw
     */
    @Around("serviceAspect()")
    public Object logTime(ProceedingJoinPoint pjp) throws Throwable {
        return doLogTime(pjp);
    }

    private Object doLogTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        SupressAspectLog annotation = signature.getMethod().getAnnotation(SupressAspectLog.class);
        if(annotation != null) {
            return pjp.proceed();
        }

        String name = signature.getDeclaringTypeName() + "." + signature.getName();

        Object[] args = pjp.getArgs();

        //长参数截断
        truncateLongParam(args);

        logStart(name, args);
        Object res = pjp.proceed();
        logEnd(name, start);

        LOGGER.trace("{}return:{}", name, JsonUtils.toJson(res));
        return res;
    }

    private void logStart(String name, Object[] args) {
        String params = JsonUtils.toJson(args);
        LOGGER.info("started:{}, params:{}", name, params);
    }

    private void logEnd(String name, long start) {
        long costMillis = System.currentTimeMillis() - start;
        LOGGER.info("finished:{}, cost:{}ms", name, costMillis);
    }

    /**
     * 这个方法一定不能改变arg所指向的对象，只能改变arg本身的指向，否则会影响方法的正确执行
     *
     * 1、截断超长字符串
     * 2、字节数组置为空数组
     * @param args
     */
    private void truncateLongParam(Object[] args) {
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                if (arg instanceof byte[]) {
                    args[i] = new byte[0];
                    continue;
                }

                if (multipartFileClazz != null && multipartFileClazz.isInstance(arg)) {
                    args[i] = null;
                    continue;
                }

                if (!(arg instanceof String)) {
                    continue;
                }
                String str = (String) arg;
                if (StringUtils.isBlank(str)) {
                    continue;
                }
                if (str.length() > paramMaxOutputLength) {
                    LOGGER.trace("截断前字符串：{}", str);
                    args[i] = str.substring(0, paramMaxOutputLength);
                }
            }
        }
    }
}
